package main

import (
	"bufio"
	"context"
	crand "crypto/rand"
	"flag"
	"fmt"
	circuit "github.com/libp2p/go-libp2p-circuit"
	"github.com/libp2p/go-libp2p-core/host"
	"io"
	"log"
	mrand "math/rand"

	golog "github.com/ipfs/go-log"
	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p-core/crypto"
	"github.com/libp2p/go-libp2p-core/network"
	ma "github.com/multiformats/go-multiaddr"
	gologging "github.com/whyrusleeping/go-logging"
)

// generateIdentity generates a RSA(2048) private key.
// If the seed is zero, use real cryptographic randomness. Otherwise, use a
// deterministic randomness source to make generated keys stay the same
// across multiple runs
func generateIdentity(seed int64) (identity crypto.PrivKey, err error) {
	var r io.Reader
	if seed == 0 {
		r = crand.Reader
	} else {
		r = mrand.New(mrand.NewSource(seed))
	}
	identity, _, err = crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
	return identity, err
}

// doEcho reads a line of data a stream and writes it back
func doEcho(s network.Stream) error {
	remote := s.Conn().RemoteMultiaddr().String()
	buf := bufio.NewReader(s)
	str, err := buf.ReadString('\n')
	if err != nil {
		log.Printf("[ERROR] Read echo stream from %s, error:%s\n", remote, err.Error())
		return err
	}

	log.Printf("[INFO] Echo stream from %s, content :%s\n", remote, str)

	_, err = s.Write([]byte(str))
	if err != nil {
		log.Printf("[ERROR] Write echo stream to %s, error:%s\n", remote, err.Error())
	}
	return err
}

func makeRelayHost(port int, hostAddress string, identity crypto.PrivKey, insecure bool) (host.Host, error) {
	opts := []libp2p.Option{
		libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/%s/tcp/%d", hostAddress, port)),
		libp2p.Identity(identity),
		libp2p.EnableRelay(circuit.OptActive, circuit.OptHop),
	}
	if insecure {
		opts = append(opts, libp2p.NoSecurity)
		log.Println("[WARN] Insecure relay")
	}
	return libp2p.New(context.Background(), opts...)
}

func main() {
	// LibP2P code uses golog to log messages. They log with different
	// string IDs (i.e. "swarm"). We can control the verbosity level for
	// all loggers with:
	golog.SetAllLoggers(gologging.INFO) // Change to DEBUG for extra info

	// Parse options from the command line
	argPort := flag.Int("l", 0, "the relay listen port")
	argHost := flag.String("h", "127.0.0.1", "the relay host listen address")
	insecure := flag.Bool("insecure", false, "use an unencrypted connection")
	seed := flag.Int64("seed", 0, "set random seed for id generation")
	flag.Parse()
	if *argPort == 0 {
		log.Fatal("[ERROR] Please provide a port to bind on with -l")
	}
	if *argHost == "" {
		log.Println("[WARN] No host is specified, the default is 127.0.0.0, which means that other computers cannot be served")
	}

	// Generate a identity for this host. We will use it at least
	// to obtain a valid host ID.
	identity, err := generateIdentity(*seed)
	if err != nil {
		log.Fatal(err)
	}

	h, err := makeRelayHost(*argPort, *argHost, identity, *insecure)
	if err != nil {
		log.Fatal(err)
	}

	h.SetStreamHandler("/echo/1.0.0", func(s network.Stream) {
		if err := doEcho(s); err != nil {
			log.Println(err)
			_ = s.Reset()
		} else {
			_ = s.Close()
		}
	})

	hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s", h.ID().Pretty()))
	addr := h.Addrs()[0]
	fullAddr := addr.Encapsulate(hostAddr)
	log.Printf("relay server address %s\n", fullAddr)

	log.Println("Ctrl + C to abort relay server")
	select {} // Hang forever.
}
